package com.bluesky.jwf.util;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import com.bluesky.javawebbrowser.domain.html.tags.Tag;
import com.bluesky.jwf.ComponentFactory;
import com.bluesky.jwf.component.CheckBox;
import com.bluesky.jwf.component.Component;
import com.bluesky.jwf.component.FileField;
import com.bluesky.jwf.component.Form;
import com.bluesky.jwf.component.Page;
import com.bluesky.jwf.component.window.Event;
import com.bluesky.jwf.component.window.MainWindow;
import com.bluesky.jwf.component.window.ScreenWindow;
import com.bluesky.jwf.component.window.Window;
import com.bluesky.jwf.component.window.WindowThread;
import com.bluesky.jwf.util.state.StateHandler;

/**
 * e.g. Foo.jwf?_jwf_event=click&_jwf_component=btn1&other_param=123 <br>
 * Foo.jwf(if current form is null or current form is not a Foo form, then
 * create it)
 * 
 * @author hz00260
 * 
 */
public class JWFServlet extends HttpServlet {
	private static Logger logger = Logger.getLogger(JWFServlet.class);

	private static String LAST_URL = "_jwf_last_url";

	public static String STATE_HANDLER_CLASS = "state.handler.class";

	private static String CONFIG_FILE = "config.file";
	private static String CHAR_ENCODING = "char.encoding";

	private String charEncoding = "UTF-8";
	private static String configFileName = "/jwf.properties";

	private static String SUPPORT_MULTITHREAD = "support.multithread";
	private boolean supportMultithread = false;

	/**
	 * there is a message thread stored the session. this is the key.
	 */
	public static String MESSAGE_THREAD_KEY_IN_SESSION = "message_thread_key_in_session";

	private StateHandler stateHandler;

	@Override
	public void init() throws ServletException {

		super.init();

		// create state handler
		String stateHandlerClassName = getInitParameter(STATE_HANDLER_CLASS);
		try {
			stateHandler = (StateHandler) Class.forName(stateHandlerClassName)
					.newInstance();
		} catch (Exception e) {

			e.printStackTrace();
			throw new RuntimeException(e);
		}

		Enumeration<String> en = getInitParameterNames();
		while (en.hasMoreElements()) {
			String key = en.nextElement();
			String value = getInitParameter(key);

			stateHandler.setParameter(key, value);
		}

		configFileName = getInitParameter(CONFIG_FILE);
		ComponentFactory.setConfigFileName(configFileName);

		charEncoding = getInitParameter(CHAR_ENCODING);
		ComponentFactory.setCharEncoding(charEncoding);

		supportMultithread = Boolean
				.parseBoolean(getInitParameter(SUPPORT_MULTITHREAD));
	}

	public String getLastUrl(HttpServletRequest req) {
		return (String) req.getSession().getAttribute(LAST_URL);
	}

	public void setLastUrl(HttpServletRequest req, String url) {
		req.getSession().setAttribute(LAST_URL, url);
	}

	protected void process(boolean post, HttpServletRequest req,
			HttpServletResponse resp) throws ServletException, IOException {
		req.setCharacterEncoding(charEncoding);

		String uri = req.getRequestURI();

		String windowName = extractFormName(req);

		if (windowName == null) {
			// not found 404
			resp.sendError(404);
			return;
		}

		// fullUrl include parameters, eg. Foo.jwf?a=123
		String fullUrl = uri;
		if (req.getQueryString() != null)
			fullUrl += "?" + req.getQueryString();

		// parse request parameters
		ServletContext context = new ServletContext(req, charEncoding);

		// try to retrieve window from session(or other place, by session id)
		MainWindow mainWindow = (MainWindow) stateHandler.get(req, context);
		if (!post) {
			/**
			 * if it is GET request.
			 */

			/*
			 * check if url same as last one, if not, create new window
			 * instance.
			 */
			if (mainWindow == null
					|| (mainWindow != null && !fullUrl.equals(getLastUrl(req)))) {
				// create new
				Window rootWindow = (Window) ComponentFactory.getInstance()
						.createComponent(ScreenWindow.class.getSimpleName());

				clearWindowThread(req);
				mainWindow = (MainWindow) ComponentFactory.getInstance()
						.createComponent(windowName);

				// pass the parameter to main window.
				Map<String, String> map = new HashMap<String, String>();
				Enumeration<String> em = req.getParameterNames();
				while (em.hasMoreElements()) {
					String key = em.nextElement();
					String value = req.getParameter(key);
					map.put(key, value);
				}
				mainWindow.setParameters(map);

				rootWindow.addChildWindow(mainWindow);
			}

		} else {
			/**
			 * if it is a POST request, then it will never create a new window
			 * instance. it is only allowed to handle event, if there is error
			 * such as no current window, report error to user.
			 */

			if (mainWindow == null) {
				// tell user wrong operation.create a blank window.
				MainWindow errorReportWindow = (MainWindow) ComponentFactory
						.getInstance().createComponent(
								MainWindow.class.getSimpleName());

				errorReportWindow.getBodyTag().setBodyAndParse(
						"<H1>Session Expired</H1><br><a href='" + fullUrl
								+ "'>start a new session</a>"

				);
				Window rootWindow = (Window) ComponentFactory.getInstance()
						.createComponent(ScreenWindow.class.getSimpleName());

				mainWindow = errorReportWindow;

				rootWindow.addChildWindow(mainWindow);
			} else {// handle the event

				ScreenWindow screenWindow = (ScreenWindow) mainWindow
						.getRootWindow();

				// String windowId = req.getParameter(PARAM_WINDOW);
				String compomentId = (String) context
						.getParameter(Form.FIELD_COMPONENT);
				String eventName = (String) context
						.getParameter(Form.FIELD_EVENT);
				String windowWidth = (String) context
						.getParameter(Form.FIELD_WINDOW_WIDTH);
				String windowHeight = (String) context
						.getParameter(Form.FIELD_WINDOW_HEIGHT);

				screenWindow.setPhysicalWidth(Integer.valueOf(windowWidth));
				screenWindow.setPhysicalHeight(Integer.valueOf(windowHeight));

				if (compomentId != null && eventName != null) {

					Component comp = (Component) screenWindow
							.getByIdInChildWindow(compomentId);
					// get the thread of the window. let it handle the event.
					if (comp == null)
						throw new RuntimeException("Component not found by id:"
								+ compomentId);

					// the active window is the window wrap the component
					Window activeWindow = (Window) comp
							.loadAncesterByClass(Window.class);

					processPostBackData(context, activeWindow);

					if (!supportMultithread)
						comp.handleEvent(eventName);
					else {
						/**
						 * try to get thread from session. if not exist create
						 * one. let the thread call the
						 * "comp.handleEvent(eventName);". in showModal()
						 * function, put the thread into a new event queue. in
						 * CLOSE event, draw the thread out of an event queue.
						 * the thread has no reference to window. the window has
						 * indirect reference to thread.
						 */
						WindowThread thread = (WindowThread) req.getSession()
								.getAttribute(MESSAGE_THREAD_KEY_IN_SESSION);
						if (thread == null) {
							Queue<Event> q = new LinkedList<Event>();
							q.add(new Event(compomentId, eventName));
							thread = new WindowThread(screenWindow, q, Thread
									.currentThread());
							req.getSession().setAttribute(
									MESSAGE_THREAD_KEY_IN_SESSION, thread);
							thread.start();
						} else {
							thread.putEvent(new Event(compomentId, eventName),
									Thread.currentThread());
						}

						Thread servletThread = Thread.currentThread();
						try {
							synchronized (servletThread) {
								servletThread.setName("servlet thread");
								servletThread.wait();
							}

						} catch (InterruptedException e) {
							throw new RuntimeException(e);

						}
					}
				}
			}
		}

		// anyway, render a web page to user.

		Page page = (Page) ComponentFactory.getInstance().createComponent(
				"Page");
		page.setUri(fullUrl);
		// set current url to session,
		setLastUrl(req, fullUrl);

		
		//in order to implement the onShow() event for Window, notify all windows here
		ScreenWindow screen=(ScreenWindow) mainWindow.getRootWindow();
		screen.notifyWindowsOfRender();
		
		
		// store the window state to (memory,fs,viewstate,etc).
		stateHandler.store(req, mainWindow, page);

		page.setWindow(mainWindow);

		// output
		resp.setCharacterEncoding(charEncoding);
		page.render(resp.getWriter());
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		// super.doPost(req, resp);
		process(true, req, resp);
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {

		/**
		 * DevAppEngineWebAppContext.handle() will dispatch q request to this
		 * servlet when a static file is not found. such as /css/example1.css,
		 * will be forwarded to /css/example1.css/HomeWindow.jwf so here we
		 * should ignore this kind of request
		 * 
		 */
		String uri = req.getRequestURI();
		int i1 = uri.indexOf('.');
		int i2 = uri.lastIndexOf('.');
		if (i1 != i2)// more than 2 dots
		{
			logger.warn("maybe file not found:" + uri);
			return;
		}

		process(false, req, resp);

	}

	private void processPostBackData(ServletContext context, Tag htmlTag) {
		logger.debug("start synchronize fields.");

		// first, set all single checkbox to unchecked, because http
		// protocol will not post any value if it is not checked
		htmlTag.traverse(new Tag.TagProcessor() {
			@Override
			public boolean process(Tag tag) {
				if (tag instanceof CheckBox) {
					CheckBox cb = (CheckBox) tag;
					cb.setChecked(false);
				}
				return false;
			}
		});

		// then copy state from request to model

		for (String name : context.getParameters().keySet()) {
			final String pName = name;
			final Object value = context.getParameters().get(name);

			// it is parameter from URL, skip it.
			if (pName.indexOf('_') < 0)
				continue;

			String[] ss = pName.split("_");
			final String componentId = ss[0];
			final String tagName = ss[1];

			logger.debug("process http param :" + pName);
			htmlTag.traverse(new Tag.TagProcessor() {
				@Override
				public boolean process(Tag tag) {
					if (tag instanceof Component) {
						Component comp = (Component) tag;
						if (comp.getId() != null
								&& comp.getId()
										.compareToIgnoreCase(componentId) == 0) {
							final String pValue;
							if (value instanceof String) {
								pValue = (String) value;
								logger.debug("set component " + pName + "="
										+ pValue);
								comp.setPostBackData(tagName, pValue);
							} else {
								FileField ff = (FileField) comp;

								FileParameter fp = (FileParameter) value;

								ff.setFileName(fp.getFileName());
								ff.setContentType(fp.getContentType());

								ff.setPostBackData(tagName, fp.getData());

								logger.debug("set component " + pName
										+ " with byte[] data");
							}

						}

					}
					return false;
				}
			});
		}
	}

	protected String extractFormName(HttpServletRequest req) {
		String uri = req.getServletPath();

		int i1 = uri.indexOf('/');
		int i2 = uri.lastIndexOf('/');
		if (i1 != i2)
			return null;

		int start = uri.lastIndexOf('/');
		int end = uri.lastIndexOf(".jwf");

		String formName = uri.substring(start + 1, end);

		return formName;
	}

	protected void clearWindowThread(HttpServletRequest req) {
		// close if exists
		WindowThread t = (WindowThread) req.getSession().getAttribute(
				MESSAGE_THREAD_KEY_IN_SESSION);
		if (t == null)
			return;

		req.getSession().removeAttribute(MESSAGE_THREAD_KEY_IN_SESSION);

		t.setQuit(true);
		synchronized (t) {
			t.notify();
		}

	}

	public static void main(String[] args) {

		// JWFServlet servlet = new JWFServlet();
		// String uri = "http://localhos:8080/myApp/Foo.jwf?a=1";
		// String formName = servlet.parseUri(uri);
		//
		// System.out.println(formName);
		Component form = ComponentFactory.getInstance().createComponent(
				"MainWindow");

		System.out.println(form.toHtml());

	}

}
